import gobject
import socket
import os
import struct
import sys

# Constants used in gobject stuff
_VOID_PYOBJ_PYOBJ = (
    gobject.SIGNAL_RUN_FIRST,
    gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)
)
_VOID_VOID = (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
_VOID_STRING = (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
               (gobject.TYPE_STRING,))

_VOID_INT_STRING = (
    gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
    (gobject.TYPE_INT, gobject.TYPE_STRING)
)

_VOID_STRING_STRING = (
    gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
    (gobject.TYPE_STRING, gobject.TYPE_STRING)
)

def read_all(sock, block_size=1024):
    """Auxiliar function that reads all data it cans"""
    chunk = sock.recv(block_size)
    yield chunk
    
    while len(chunk) == block_size:
        chunk = sock.recv(block_size)
        yield chunk

class UnblockedSocket:
    """Unblocked socket, uses os.read/write instead of actual recv/send"""
    def __init__(self, *args, **kwargs):
        self.sock = socket.socket(*args, **kwargs)
        self.sock.setblocking(1)
        self.block_size = 1024
        
    def recv(self, val):
        return os.read(self.fileno(), self.block_size)
    
    def send(self, data):
        return os.write(self.fileno(), data)
    
    def __getattr__(self, attr):
        return getattr(self.sock, attr)


def create_tcp_socket():
    """Creates an UnblockedSocket object"""
    if sys.platform == "win32":
        return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    else:
        return UnblockedSocket(socket.AF_INET, socket.SOCK_STREAM)

class SocketConnection(gobject.GObject):
    __gsignals__ = {
        # Connection life cycle
        "connected": _VOID_VOID,
        "disconnected": _VOID_VOID,
        
        # Data interaction
        "sent-data": _VOID_STRING,
        "received-data": _VOID_STRING,
    }

    def __init__(self, sock, block_size=2048):
        self.sock = sock
        gobject.GObject.__init__(self)
        self.block_size = block_size
    
    def start(self):
        gobject.io_add_watch(self.sock.fileno(), gobject.IO_IN, self.on_read)
        self.emit("connected")
    
    def on_read(self, sock, condition):
        try:
            data = "".join(read_all(self.sock, self.block_size))
            is_alive = len(data) > 0
        except socket.error:
            is_alive = False
        
        if is_alive:
            self.emit("received-data", data)
        else:
            self.stop()
            
        return is_alive
        
    def send(self, data):
        try:
            self.sock.send(data)
            self.emit("sent-data", data)
        except socket.error:
            self.stop()
        
    def stop(self):
        self.sock.close()
        self.emit("disconnected")

def prepare_dispatcher_socket(sock, port):
    """Makes a socket server to be dispatched"""
    sock.bind(('', port))
    sock.listen(1)
    return sock

class ConnectionDispatcher(gobject.GObject):
    
    __gsignals__ = {
        "new-connection": _VOID_PYOBJ_PYOBJ,
        "lost-connection": _VOID_PYOBJ_PYOBJ,
        "connected": _VOID_VOID,
        "disconnected": _VOID_VOID,
    }

    def __init__(self, sock=None, connection_factory=SocketConnection):
        """Socket must already called 'bind' before"""
        
        self.sock = sock
        self.connections = {}
        self.addresses = {}
        self.connection_factory = connection_factory
        gobject.GObject.__init__(self)

    def start(self, port=None, sock=None, socket_factory=create_tcp_socket):
        if port is not None:
            if sock is None:
                sock = socket_factory()
            prepare_dispatcher_socket(sock, port)
        
        if sock is not None:
            self.sock = sock
            
        gobject.io_add_watch(self.sock.fileno(), gobject.IO_IN, self.on_accepted)
        self.emit("connected")
        
    def on_accepted(self, sock, condition):
        try:
            sock, addr = self.sock.accept()
        except socket.error:
            self.stop()
            return
        
        # Create the socket connection object
        conn = self.connection_factory(sock)
        
        self.connections[addr] = conn
        self.addresses[conn] = addr
        
        # Make sure we remove the sockets when they die
        conn.connect("disconnected", self.on_disconnected)
        
        # Finally emit the new connection signal
        self.emit("new-connection", conn, addr)
        
        # Start the connection, so that it begins as well
        conn.start()
        return True
    
    def on_disconnected(self, connection):
        try:
            addr = self.addresses.pop(connection)
            self.connections.pop(addr)
            self.emit("lost-connection", connection, addr)
        except KeyError:
            pass

    def get_connections(self):
        return self.addresses.keys()
    
    def get_addresses(self):
        return self.connections.keys()

    def stop(self):
        # Broadcast termination
        for conn in self.get_connections():
            conn.stop()
            
        # Close main socket
        self.sock.close()
        
        # Warn listeners
        self.emit("disconnected")


class PacketReader(gobject.GObject):

    __gsignals__ = {
        "client-list": _VOID_STRING_STRING,
        "broadcast": _VOID_STRING_STRING,
        "pvt-message": _VOID_STRING_STRING,
    }

    packets = {
        "\x00": "client-list",
        "\xFF": "broadcast",
        "\x01": "pvt-message",
    }
    
    def __init__(self, conn):
        self.connection = conn
        self.reading_header = True
        self.data = ""
        conn.connect("received-data", self.on_received_data)
        
    
    def on_received_data(self, conn, data):
        if self.reading_header:
            self.data += data
            self.reading_data = len(self.data) < 3

            if not self.reading_header:
                self.type = self.data[0]
                self.size = pack.unpack(self.data[1:2], "H")
                self.body = self.data[3:]
                data = ""
                
        if not self.reading_header:
            self.body += data
            if len(self.body) < self.size:
                return
            # Read a packet
            self.emit(self.packets[self.type], self.body[:self.size])

            # Remove what we've read so far, maybe we have another
            # packet
            self.reading_header = True
            self.data = ""
            
            # Remaining data contained in the body
            data = self.body[:-len(self.body) - self.size]
            
            if len(data) > 0:
                # Try again
                self.on_received_data(data)
            

        

